package com.googlecode.animator.view.viewport
{
	import com.googlecode.animator.data.cameras.CameraVO;
	import com.googlecode.animator.data.objects3d.CameraProxyVO;
	import com.googlecode.animator.data.objects3d.Object3DVO;
	import com.googlecode.animator.data.objects3d.TargetProxyVO;
	import com.googlecode.animator.utils.ScreenTo3D;
	
	import flash.events.ContextMenuEvent;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.ui.ContextMenu;
	import flash.ui.ContextMenuItem;
	
	import mx.core.UIComponent;
	
	import org.papervision3d.cameras.Camera3D;
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.core.utils.InteractiveSceneManager;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.view.layer.ViewportLayer;

	/**
	 * The viewport component enables a view into the 3D world, as seen through a camera. Multiple
	 * viewports can be set up to allow different simultaneous views of the same 3d world.
	 *  
	 * @author USER
	 * 
	 */
	public class Viewport extends UIComponent
	{
		private var _vWidth:int;		
		private var _vHeight:int;					
		private var _ISM:InteractiveSceneManager;
		
		protected var _renderer:BasicRenderEngine;
		protected var _camera:Camera3D;	
		protected var _zoomScale:int;	
		protected var _viewport:Viewport3D;
		protected var _scene:Scene3D;
		protected var _currentCamera:String;
		protected var _label:String;
		protected var _convertPos:ScreenTo3D;
		protected var _plane3DOrientation:Number3D;
		protected var _selectedObject3D:DisplayObject3D;
		protected var _dragObject3DOffset:Number3D;
		protected var _dragging:Boolean;
		
		public static const OBJECT3D_SELECTED:String = "object3DSelected";
		public static const OBJECT3D_RELEASED:String = "object3DReleased";
		public static const OBJECT3D_DELETE:String = "object3DDeleted";
		
		
		/**
		 * Constructor. 
		 * 
		 */
		public function Viewport()
		{
			super();

		}
				
		/**
		 * Sets up the viewport. 
		 * 
		 */
		public function setupViewport():void
		{
			// create viewport from papervision3d classes
			_viewport = new Viewport3D(vWidth, vHeight, false, false);
			_viewport.interactive = true;
			_viewport.graphics.beginFill(0xff0000, 0);
			_viewport.graphics.drawRect(0, 0, vWidth, vHeight);			
			
			addChild(_viewport);
			
			// viewport has a permanent camera. It can be set-up as needed.
			_camera = new Camera3D();
			
			// each viewport has a 3D plane to enable mouse clicks to be captured in 3D space
			_convertPos = new ScreenTo3D(_viewport);
			
			//_ISM = _viewport.interactiveSceneManager;
			//_ISM.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, onEvent3D);
			
			addEventListener(Event.REMOVED, onViewportRemoved);
			
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Local 3D Object functions: each viewport can have 3D objects, not
		// not part of the PureMVC framework.
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Adds a 3D object to the scene. 
		 * 
		 */
		public function addViewportObject3D(object3D:DisplayObject3D):void
		{
			_scene.addChild(object3D);
		}
		
		/**
		 * Removes existing grid from the scene. 
		 * 
		 */
		public function removeViewportObject3D(name:String):void
		{	
			// remove event listeners.
			if(_scene.getChildByName(name) == null)
			{
				return;
			}
			
			if(_viewport.getChildLayer(_scene.getChildByName(name)) != null)
			{
				var vpl:ViewportLayer = _viewport.getChildLayer(_scene.getChildByName(name));
				vpl.removeEventListener(MouseEvent.MOUSE_DOWN, onObjectSelected);
				
				if(vpl.contextMenu != null)
				{
					vpl.contextMenu.removeEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onDeleteObject);
					
				}
			} 
			
			
			_scene.removeChildByName(name);
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// 3D Plane functions: each viewport has a 3D plane to detect mouse clicks in 3D space.
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Sets up the 3D plane, used for tracking user clicks in 3D space. 
		 * 
		 */
		public function setup3DPlane(pt:Number3D=null):void
		{
			_convertPos.setViewPlane(_plane3DOrientation, pt);
		}
		
		/**
		 * Orients the 3D Plane in 3D space. 
		 * @param plane
		 * 
		 */
		public function set plane3DOrientation(plane:String):void
		{
			var normal:Number3D = new Number3D(0, 0, 0);
			
			switch(plane)
			{
				case "xz":
					normal.y = 1;
					_plane3DOrientation = normal;
					break;
				case "xy":
					normal.z = 1;
					_plane3DOrientation = normal;
					break;
				case "zy":
					normal.x = 1;
					_plane3DOrientation = normal;
					break;				
				
			}
			
			//_viewportOrientation = plane;
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Camera functions: each viewport requires a 3D camera.
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Changes the viewport camera to whatever is sent. 
		 * @param camera
		 * 
		 */
		/* public function setViewportCamera(camera:Camera3D):void
		{
			_camera = camera;
			render();
		} */
		
		/**
		 * Set the current camera to null. 
		 * 
		 */
		public function removeViewportCamera():void
		{
			_camera = null;
			_currentCamera = null;
		}
		
		/**
		 * Updates camera position and zoom properties. 
		 * @param VO
		 * 
		 */
		public function updateViewportCamera(camera:CameraVO):void
		{				
			_camera.x = camera.x;
			_camera.y = camera.y;
			_camera.z = camera.z;
			_camera.zoom = camera.zoom;
			_camera.roll(camera.roll);
			_camera.yUP = camera.yUp == "false" ? false : true;
			
			var target:DisplayObject3D = new DisplayObject3D();
			target.x = camera.target.x;
			target.y = camera.target.y;
			target.z = camera.target.z;
			
			_camera.lookAt(target);			
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// 3D Object functions
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Carries out any necessary work on a 3D object added to the scene. 
		 * @param object3D
		 * 
		 */
		public function addObject(object3D:Object3DVO):void
		{
			// add event listeners to capture mouse down events.
			// these should be removed once the object is removed.
			var vpl:ViewportLayer = _viewport.getChildLayer(_scene.getChildByName(object3D.name));
			vpl.addEventListener(MouseEvent.MOUSE_DOWN, onObjectSelected);
			
			// add right-click listener, if the object3D is not a proxy object
			if(object3D is CameraProxyVO || object3D is TargetProxyVO)
			{			
				return;
			}
			
			var menuItem:ContextMenuItem = new ContextMenuItem("Delete Object");
			menuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onDeleteObject);
			
			var customContextMenu:ContextMenu = new ContextMenu();
			customContextMenu.hideBuiltInItems();
			customContextMenu.customItems.push(menuItem);
			
			vpl.contextMenu = customContextMenu;
			
			render();
			
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Utility functions
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Makes the named object3D invisible in this viewport. 
		 * @param name
		 * 
		 */
		public function makeViewportObject3DInvisible(name:String):void
		{
			var child:DisplayObject3D = _scene.getChildByName(name);
			var vpl:ViewportLayer = _viewport.getChildLayer(child);
			vpl.mouseEnabled = false;
			vpl.visible = false;
		}
		
		/**
		 * Makes the named object3D visible in this viewport. 
		 * @param name
		 * 
		 */
		public function makeViewportObject3DVisible(name:String):void
		{
			 var child:DisplayObject3D = _scene.getChildByName(name);
			var vpl:ViewportLayer = _viewport.getChildLayer(child);
			vpl.mouseEnabled = true;
			vpl.visible = true; 
		}
		
		/**
		 * Renders this viewport. 
		 * 
		 */
		public function render():void
		{
			if(_camera == null)
			{
				return;
			}
	
			var pt:Number3D = _convertPos.get3DPoint(_camera);
			
			// enables the object3D to be dragged
			if(_selectedObject3D != null)
			{
				_selectedObject3D.x = pt.x + _dragObject3DOffset.x;
				_selectedObject3D.y = pt.y + _dragObject3DOffset.y;
				_selectedObject3D.z = pt.z + _dragObject3DOffset.z;	
			} 

			_renderer.renderScene(_scene, _camera, _viewport);
		}
		
		/**
		 * Centres viewport. 
		 * 
		 */
		public function centreViewport():void
		{
			var offsetX:Number = (vWidth - this.parent.width) / 2;
			var offsetY:Number = (vHeight - this.parent.height) / 2;
			
			var viewArea:Rectangle = new Rectangle(offsetX, offsetY, this.parent.width, this.parent.height);
			_viewport.scrollRect = viewArea;
		}
		
		/**
		 * Cleans up viewport before it is removed. 
		 * 
		 */
		public function destroy():void
		{			
			_viewport = null;
			_camera = null;
			_renderer = null;
			
			
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Event handlers
		//
		//////////////////////////////////////////////////////////////////////		
		
		/**
		 * Responds to mouse up events.
		 * @param evt
		 * 
		 */
		public function mouseUpAnywhere():void
		{	
			_dragging = false;
		}
		
		/**
		 * Responds to 3D events. Not used. 
		 * 
		 */
		protected function onEvent3D():void
		{
			// curently not used
		}
		
		/**
		 * Responds to when the user clicks down on a 3D object.
		 * @param evt
		 * 
		 */
		protected function onObjectSelected(evt:MouseEvent):void
		{		
				
			// store reference to the selected object
			_selectedObject3D = (evt.currentTarget as ViewportLayer).displayObject3D;
			
			// get the vector from the click point to the object(origin).
			var origin:Number3D = new Number3D(_selectedObject3D.x, _selectedObject3D.y, _selectedObject3D.z);
			
			// ensure the Plane3D bisects the object centre	
			setup3DPlane(new Number3D(_selectedObject3D.x, _selectedObject3D.y, -_selectedObject3D.z));			
			
			/* if(this.id == "topView")
			{
				setup3DPlane(new Number3D(_selectedObject3D.x, -_selectedObject3D.y, _selectedObject3D.z));
			} */
	
			var clickPt:Number3D = _convertPos.get3DPoint(_camera);
			
			_dragObject3DOffset = Number3D.sub(origin, clickPt);
			
			// if an 3D object has been clicked	on, it is now draggable
			// _dragging = true;
			
			// dispatch an event to alert any listeners that a 3D object has been selected
			dispatchEvent(new Event(OBJECT3D_SELECTED));		
		}
		
		/**
		 * Triggered by user right-clicking on 3D object. 
		 * 
		 */
		protected function onDeleteObject(evt:ContextMenuEvent):void
		{
			var object3D:DisplayObject3D = (evt.contextMenuOwner as ViewportLayer).displayObject3D;
			
			// remove event listeners to capture mouse down events.
			var vpl:ViewportLayer = _viewport.getChildLayer(_scene.getChildByName(object3D.name));
			vpl.removeEventListener(MouseEvent.MOUSE_DOWN, onObjectSelected);
			vpl.contextMenu.removeEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onDeleteObject);
			
			_selectedObject3D = object3D;
			
			dispatchEvent(new Event(OBJECT3D_DELETE));
			
		}
		
		/**
		 * Removes the mediator when this component is removed. 
		 * @param evt
		 * 
		 */
		protected function onViewportRemoved(evt:Event):void
		{
			removeEventListener(Event.REMOVED, onViewportRemoved);
			destroy();
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Overriden Flex functions
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Called by Flex in response to the display list being updated and the stage being redrawn.
		 *  
		 * @param unscaledWidth
		 * @param unscaledHeight
		 * 
		 */
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);

			if(_viewport != null)
			{
				centreViewport();
			}
						
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Public getters and setters
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Gets a reference to the viewport. 
		 * @return 
		 * 
		 */
		public function get viewport():Viewport3D
		{
			return _viewport;
		}
		
		/**
		 * Sets and gets the viewport width. 
		 * @param value
		 * 
		 */
		public function set vWidth(value:int):void
		{
			_vWidth = value;
		}
		
		public function get vWidth():int
		{
			return _vWidth;
		}
		
		/**
		 * Sets and gets the viewport height.
		 * @param value
		 * 
		 */
		public function set vHeight(value:int):void
		{
			_vHeight = value;
		}
		public function get vHeight():int
		{
			return _vHeight;
		}

		/**
		 * Set a reference to the 3D scene so that it can be used locally. 
		 * @param s
		 * 
		 */
		public function set scene(s:Scene3D):void
		{
			_scene = s;

		}
		
		/**
		 * Set a reference to the renderer so that it can be used locally.
		 * @param r
		 * 
		 */
		public function set renderer(r:BasicRenderEngine):void
		{
			_renderer = r;
		}
		
		/**
		 * Sets and gets the label for this viewport. 
		 * @param l
		 * 
		 */
		public function set label(l:String):void
		{
			_label = l;
		}
		
		public function get label():String
		{
			return _label;
		}
		
		/**
		 * Sets and gets the id of the current camera for this viewport. 
		 * @param cameraID
		 * 
		 */
		public function set currentCamera(cameraID:String):void
		{
			_currentCamera = cameraID;
		}
		
		public function get currentCamera():String
		{
			return _currentCamera;
		}
		
		/**
		 * Gets and sets the zoom factor to be used in scaling calculations. 
		 * @param z
		 * 
		 */
		public function set zoomScale(z:int):void
		{
			_zoomScale = z;
		}
		
		public function get zoomScale():int
		{
			return _zoomScale;
		}
		
		/**
		 * Gets and sets the zoom for this viewport. 
		 * @param z
		 * 
		 */
		public function set zoom(z:int):void
		{
			
			_camera.zoom = z;
		}
		
		public function get zoom():int
		{
			return _camera.zoom;
		} 
		
		/**
		 * Returns the name of the DisplayObject3D currently selected within this viewport.
		 * @return 
		 * 
		 */
		public function get selectedObject3D():String
		{
			return _selectedObject3D.name;
		}
		
		public function set dragObject(object3D:DisplayObject3D):void
		{
			_selectedObject3D = object3D;
		}
	}
}